package com.nowcoder.community.service;

import com.alibaba.fastjson.JSON;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.entity.DiscussPost;
import com.nowcoder.community.util.SensitiveFilter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.util.HtmlUtils;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Service
public class DiscussPostService {
    private static final Logger logger = LoggerFactory.getLogger(DiscussPostService.class);

    private ScheduledExecutorService task = new ScheduledThreadPoolExecutor(10, new BasicThreadFactory.
            Builder().namingPattern("clearCache-schedule-pool-%d").build());
    private final DiscussPostMapper discussPostMapper;
    private final SensitiveFilter sensitiveFilter;
    private final RedisTemplate redisTemplate;
    //本地缓存
    @Value("${caffeine.posts.max-size}")
    private int maxSize;
    @Value("${caffeine.posts.expire-seconds}")
    private int expireSeconds;
    //Caffeine核心接口: Cache, LoadingCache(同步), AsyncLoadingCache(异步)
    //帖子列表缓存
    private LoadingCache<String, List<DiscussPost>> postListCache;
    //帖子总数缓存
    private LoadingCache<Integer, Integer> postRowsCache;
    public DiscussPostService(DiscussPostMapper discussPostMapper, SensitiveFilter sensitiveFilter, RedisTemplate redisTemplate) {
        this.discussPostMapper = discussPostMapper;
        this.sensitiveFilter = sensitiveFilter;
        this.redisTemplate = redisTemplate;
    }

    @PostConstruct//会在服务器加载Servlet的时候运行，并且只运行一次
    public void init(){
        //初始化帖子列表缓存
        postListCache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                .build(new CacheLoader<String, List<DiscussPost>>() {
                    @Nullable
                    @Override
                    public  List<DiscussPost> load(@NonNull String key) throws Exception {
                        if (key == null || key.length() == 0) {
                            throw new IllegalArgumentException("参数错误！");
                        }
                        String[] params = key.split(":");
                        if (params == null || params.length != 4) {
                            throw new IllegalArgumentException("参数错误！");
                        }
                        int userId = Integer.valueOf(params[0]);
                        int offset = Integer.valueOf(params[1]);
                        int limit = Integer.valueOf(params[2]);
                        int orderMode = Integer.valueOf(params[3]);
                        //二级缓存:Caffeine -> redis -> mysql
                        String discussPostsKey = "DiscussPosts" + ":" + userId + ":" + offset + ":" + limit + ":" + orderMode;
                        String discussPostsString = (String)redisTemplate.opsForValue().get(discussPostsKey);

                        if(StringUtils.isNoneBlank(discussPostsString)){
                            List<DiscussPost> discussPosts = JSON.parseArray(discussPostsString, DiscussPost.class);
                            logger.debug("redis cache");
                            return discussPosts;
                        }else{
                            //使用布隆过滤器防止缓存穿透（恶意制作加ticket）黑名单使用方法
                            List<DiscussPost> discussPosts = discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
                            //差异化失效时间防止缓存雪崩
                            Random random = new Random();
                            redisTemplate.opsForValue().set(discussPostsKey, JSON.toJSONString(discussPosts), (long) (2 + random.nextInt(2)), TimeUnit.HOURS);
                            logger.debug("load post list from DB");
                            return discussPosts;

                        }

                    }
                });

        //初始化帖子总数缓存
        postRowsCache = Caffeine.newBuilder()
                .maximumSize(maxSize)
                .expireAfterWrite(expireSeconds, TimeUnit.SECONDS)
                .build(new CacheLoader<Integer, Integer>() {
                    @Nullable
                    @Override
                    public Integer load(@NonNull Integer key) throws Exception {
                        logger.debug("load post list from DB");
                        return discussPostMapper.selectDiscussPostRows(key);
                    }
                });

    }

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit, int orderMode) {
        if(userId==0){
            return postListCache.get(userId+":" + offset +":" + limit + ":" + orderMode);
        }else {
            logger.debug("load post list from DB");
            return discussPostMapper.selectDiscussPosts(userId, offset, limit, orderMode);
        }
    }

    public int findDiscussPostRows(int userId) {
        if(userId == 0){
            return postRowsCache.get(userId);
        }else {
            logger.debug("load post rows from DB.");
            return discussPostMapper.selectDiscussPostRows(userId);
        }
    }

    public DiscussPost findDiscussPostById(int id) {
        return discussPostMapper.selectDiscussPostById(id);
    }

    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }

        //每次更新删除本地缓存和redis缓存（双写一致性）
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        Set<String> keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);


        //  转义HTML标记
        post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
        post.setContent(HtmlUtils.htmlEscape(post.getContent()));
        //  过滤
        post.setTitle(sensitiveFilter.filter(post.getTitle()));
        post.setContent(sensitiveFilter.filter(post.getContent()));
        int x = discussPostMapper.insertDiscussPost(post);

        //缓存双删（防止脏数据），弱一致性
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        task.schedule(() -> {
            postListCache.invalidateAll();
            postRowsCache.invalidateAll();
        }, 2L, TimeUnit.SECONDS);

        keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);

        return x;
    }


    public int updateCommentCount(int id, int commentCount) {
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        Set<String> keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        int x = discussPostMapper.updateCommentCount(id, commentCount);
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        task.schedule(() -> {
            postListCache.invalidateAll();
            postRowsCache.invalidateAll();
        }, 2L, TimeUnit.SECONDS);
        //缓存双删（防止脏数据）
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        return x;
    }

    public int updateType(int id, int type){
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        Set<String> keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }
        task.schedule(() -> {
            postListCache.invalidateAll();
            postRowsCache.invalidateAll();
        }, 2L, TimeUnit.SECONDS);
        int x = discussPostMapper.updateType(id, type);
        //缓存双删（防止脏数据）
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        return x;
    }

    public int updateStatus(int id, int status){
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        Set<String> keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);

        int x =  discussPostMapper.updateStatus(id, status);
        //缓存双删（防止脏数据）
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        return x;
    }

    public int updateScore(int id, double score){
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        Set<String> keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        int x = discussPostMapper.updateScore(id, score);
        //缓存双删（防止脏数据）
        postListCache.invalidateAll();
        postRowsCache.invalidateAll();
        keys = redisTemplate.keys("DiscussPosts:*");
        redisTemplate.delete(keys);
        return x;
    }

}
